Skip to content

chore: pin Cloud Functions + Flutter client to europe-west2 (EU migration)#159

Merged
code418 merged 8 commits into
masterfrom
chore/eu-region
Jun 14, 2026
Merged

chore: pin Cloud Functions + Flutter client to europe-west2 (EU migration)#159
code418 merged 8 commits into
masterfrom
chore/eu-region

Conversation

@code418

@code418 code418 commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Roadmap v1.3 — the reversible, code-only groundwork for the us-central1 → europe-west2 migration, plus the read-only tool to verify the data move.

⚠️ Do not deploy on merge

All of this is code-only and inert until the operator-run migration happens, and the order is load-bearing:

  1. Firestore nam5 → eur3 cut-over — export to a US bucket, copy US→EU, import to the recreated eur3 DB (destructive gcloud work, not in this PR)
  2. firebase deploy --only functions → creates the europe-west2 copies
  3. ship the Flutter build from this PR

Deploying functions before the Firestore move adds a cross-Atlantic hop; shipping the client before the functions exist in europe-west2 points the app at nothing.

Phase 1 — backend region pin (e821b87)

  • New functions/src/_region.ts owns FUNCTION_REGION and calls setGlobalOptions({ region }), pinning all 9 v2 functions (8 callables + newDayScoreboard) in one place; imported first in index.ts so it runs before any function is defined.
  • The two v1 triggers (onUserCreated, onFriendAdded) reference the same constant via .region().
  • Test guard asserts every export's __endpoint.region includes europe-west2.

Phase 4 — client region pin (f208ccc)

  • New lib/firebase_functions_eu.dart exposes appFunctionsinstanceFor(region: 'europe-west2'), the single region-pinned seam.
  • All 11 callable call sites switched to appFunctions.
  • New test/firebase_functions_region_test.dart scans lib/ so any future call site on the US-default instance fails CI.

Migration verification CLI (39cb264)

  • functions/src/scripts/verify_migration.ts (npm run verify-migration): read-onlysnapshot counts every root collection (listCollections + count() aggregations) plus nested groups, never reading bodies or writing; compare diffs two snapshots and exits non-zero on any mismatch. Run before (nam5) and after (eur3) on frozen data — the step-G gate before reopening traffic.
  • Pure diffSnapshots() extracted to _migrationVerify.ts and unit-tested (a verify tool that falsely reports "match" is the worst failure mode).

Verification

  • cd functions && npm test376 passing; npm run lint clean; 11/11 endpoints on europe-west2.
  • flutter analyze clean; flutter test390 passing.

Still to do (operator/console, out of scope)

Firestore nam5→eur3 move (US→EU bucket hop), functions deploy, app release, then decommission us-central1 callables + the old newDayScoreboard scheduler after one zero-traffic release cycle.

🤖 Generated with Claude Code

code418 and others added 2 commits June 14, 2026 22:06
Roadmap v1.3 step 2 of the us-central1 -> europe-west2 migration: a
code-only region pin, no deploy yet (deploying before the Firestore
nam5 -> eur3 cut-over would add a cross-Atlantic hop and worsen latency).

- New _region.ts owns FUNCTION_REGION and calls setGlobalOptions, covering
  all 9 v2 functions (callables + scheduler) in one place. Imported first in
  index.ts so it runs before any function module is evaluated.
- The two v1 triggers (onUserCreated, onFriendAdded) reference the same
  constant via .region().
- Test guard iterates every export and asserts __endpoint.region includes
  europe-west2, so a future function that forgets the region fails CI.

Verified: 11/11 endpoints report ["europe-west2"]; npm test 372 passing;
lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roadmap v1.3 step 5: pin the Flutter client to the europe-west2 Cloud
Functions so it stays in-region once the functions are deployed there.

- New lib/firebase_functions_eu.dart exposes `appFunctions`
  (FirebaseFunctions.instanceFor(region: 'europe-west2')) — the single
  region-pinned seam. The default FirebaseFunctions.instance targets
  us-central1.
- All 11 callable call sites switched from FirebaseFunctions.instance to
  appFunctions (nearby, claim_quiz_sheet, wear x2, route x2, reports, admin,
  user_repository, claim_history_screen, notification_service). cloud_functions
  imports kept only where a cloud_functions type is still used.
- New test/firebase_functions_region_test.dart guards the helper region and
  scans lib/ so a future call site using the US-default instance fails CI.

Inert until the functions exist in europe-west2 (instanceFor only changes the
target URL), so this ships with/after the functions deploy, not before.

Verified: flutter analyze clean; region guard 2/2; flutter test 390 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@code418 code418 changed the title chore(functions): pin all Cloud Functions to europe-west2 chore: pin Cloud Functions + Flutter client to europe-west2 (EU migration) Jun 14, 2026
code418 and others added 6 commits June 14, 2026 22:39
Step G of the EU migration (ROADMAP v1.3): confirm the eur3 import is
complete before reopening traffic.

- scripts/verify_migration.ts: read-only CLI. `snapshot` counts every root
  collection (via listCollections + count() aggregations — never reads bodies,
  never writes) plus the nested groups entries/countyStats/counties/periods,
  writing a JSON snapshot. `compare` diffs two snapshots and exits non-zero on
  ANY mismatch. Run before (nam5) and after (eur3) on frozen data.
- _migrationVerify.ts: pure diffSnapshots() extracted so the diff verdict is
  unit-testable — a verify tool that falsely reports "match" is the worst
  failure mode. 4 new tests (match / mismatch+delta / one-sided / groups).
- npm run verify-migration script (mirrors plan-route).

Verified: build + lint clean; TS suite 376 passing; compare smoke-tested both
ways (matching -> exit 0, mismatch -> exit 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v1.3 migration plan said to export to a europe-west2 bucket, but a nam5
DB can only export to a US bucket (and eur3 can only import from an EU one).
Correct to the two-bucket US->EU copy flow, and fix related inaccuracies found
while executing:
- two buckets (US for export, EU for import) + the cp -r hop between them
- verify step now references the read-only verify-migration CLI + full
  collection list (incl. reports/reportQuotas)
- deploy step: keep the 8 us-central1 callables but delete the 3 event-driven
  functions to avoid double-firing; verify 11 (not 8) healthy
- client-pin step marked done (11 call sites, not 8; claim.dart was wrong)
- storage step notes the default bucket holds report_photos/ + osm_changesets/
- rollback imports the pre-migration backup from the US bucket

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v1 (Gen1) Firestore trigger onFriendAdded can't deploy to europe-west2
against an eur3 database: Gen1 Firestore triggers must run in the DB's region,
and eur3's Gen1 trigger region is europe-west1, not europe-west2. The first
EU deploy failed with "...is in region eur3-europe-west1 which is not
supported" (the other 10 functions deployed fine).

- New FIRESTORE_TRIGGER_REGION = "europe-west1" in _region.ts; onFriendAdded
  uses it instead of FUNCTION_REGION.
- onUserCreated stays on europe-west2 (Auth triggers are global, not DB-bound).
- Region guard updated with a per-function override map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
eur3 has no Gen1 Firestore triggers — a v1 trigger deploy fails in every
region ("...is in region eur3-europe-west1 which is not supported"), so
europe-west1 didn't help either. Convert onFriendAdded to a 2nd-gen
onDocumentUpdated trigger; Eventarc maps eur3 -> europe-west4, so it runs
there.

- FIRESTORE_TRIGGER_REGION = europe-west4; onFriendAdded uses v2
  onDocumentUpdated (firebase-functions/v2/firestore), dropping functionsV1.
- Region guard now also asserts onFriendAdded is gcfv2, to catch a silent
  regression back to a v1 trigger (which eur3 can't run).

Verified: build + lint clean; suite 377 passing; runtime endpoint reports
region europe-west4, platform gcfv2, eventTrigger true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First v1.3 "Platform foundations" release: Cloud Functions + Firestore now
in europe-west2 / eur3, client pinned to europe-west2 via appFunctions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@code418 code418 merged commit ce0a2fd into master Jun 14, 2026
2 of 3 checks passed
@code418 code418 deleted the chore/eu-region branch June 14, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant